package org.NooLab.openNLP;

import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import org.NooLab.itexx.comm.tcp.box.TcpBox;
import org.NooLab.itexx.comm.tcp.box.infra.DataSenderIntf;
import org.NooLab.nlp.structures.NLPresultSet;
import org.NooLab.nlp.structures.NlpDataPackage;
import org.NooLab.openNLP.app.LinguistProperties;
import org.NooLab.openNLP.components.LingSources;
import org.NooLab.openNLP.components.LinguisticProcessor;
import org.NooLab.openNLP.components.SearchTermsPreparation;
import org.NooLab.structures.infra.remotely.ActionRequest;
import org.NooLab.structures.nlp.NlpDataRequest;
import org.NooLab.structures.nlp.NlpProcessInfo;
import org.NooLab.structures.nlp.WordSets;
import org.NooLab.utilities.logging.PrintLog;
import org.NooLab.utilities.net.GUID;
import org.NooLab.utilities.objects.StringedObjects;
import org.NooLab.utilities.strings.StringsUtil;




class NLPmain {

	
	NlpFactory lpFactory;
	LinguistProperties lpProperties;
	NLPmain nlp ;
	
	LingSources lingSources = new LingSources(); 
	LinguisticProcessor lingproc;

	int sentResultsCount=0;
	int receivedStringsCount=0;
	
	private TcpBox tcp;
	
	OutBox outbox;
	InBox inbox;
	boolean inBoxIsRunning=false;
	boolean outBoxIsRunning = false;
	
	PrintLog out = new PrintLog(2,false);
	StringsUtil strgutil = new StringsUtil();
	StringedObjects so = new StringedObjects();
	private boolean isActive;
	// ========================================================================
	public NLPmain(NlpFactory factory) {
		// 
		lpFactory = factory;
		
		lpProperties = lpFactory.lpProperties ;
		
		nlp = this;
		
		inbox = new InBox(this);
		
		outbox = new OutBox(this);
		
		isActive = true;
	}
	// ========================================================================
	
	public void close(int code){

		inBoxIsRunning=false ;
		outBoxIsRunning = false ;

		if (outbox!=null){
			outbox.send( "NooNLP StateMessage : application will close.") ;
		}
		
		out.print(1, "NooNLP for requested language <"+lpProperties.getActiveLanguage()+"> closed.");
		
		PrintLog.Delay(300) ;
		if (code<=0){
			System.exit(code) ;
		}
	}

	public boolean isActive() {

		return isActive;
	}

	public void perform() throws Exception{
		
		
		try{
			
			
			
			lingproc = new LinguisticProcessor(lpProperties,2); 
				
											out.print(2, "preparing main process, communication...") ;

			// any results from lingproc will be received by the OutBox, 
			// which sends the data via TCP to the contractor
			lingproc.activateObservation( (Observer) outbox);

			
		}catch(Exception e){
			
			out.printErr(1, "NooNlp could not start the LinguisticProcessor for the requested language <"+lpProperties.getActiveLanguage()+">.\n "+
					        "        "+e.getMessage());
			inbox.start() ;
			outbox.start() ; out.delay(500);
			
			sendFailureToClient() ;
			close(-8);
		}
		
		
		lingproc.setSpeed(100); // concerns "sync'ing of delivery process and consuming processing
		
		lingproc.setCoreCompressionLevel(6); // 1..10,  10 = strong -> short core phrases, 1 = low -> long core phrase

											out.print(2, "preparing main process...") ;
		// the inbox receives the messages form the TCP box = tasks
		// and invokes the LinguisticProcessor's analyze 									
		inbox.start() ;
		outbox.start() ; 
		
		tcp = lpFactory.getTcpBox() ;
		
		tcp.getTcpSender();
		tcp.deleteObservers();
		
		tcp.addTcpObserver(inbox) ;

		String mstr = "Preparations of NLP module for language <"+lpProperties.getActiveLanguage()+"> done, ready to work.";
		out.print(2, mstr ) ;
		out.delay(10);
		
		
		try {
			
			if (outbox==null){
				outbox = new OutBox(this);
				out.delay(50);
			}
			DataSenderIntf sender = outbox.tcp.getTcpSender();
			// prepare a process message
			sender.send( new NlpProcessInfo( mstr,"nlp",lpProperties.getActiveLanguage(),System.currentTimeMillis()) ) ;

		} catch (Exception e) {
			// 
			e.printStackTrace();
		}

	}

	public int getSentResultsCount() {
		return sentResultsCount;
	}

	public int getReceivedStringsCount() {
		return receivedStringsCount;
	}

	public void sendFailureToClient() {
		// 
		String msg;
		
		msg = "@itexx: linguistic handling of textual data failed. (code:-8)" ;
		out.printErr(1, msg) ; // will be received through process observation
		
		try {
		
			DataSenderIntf sender = outbox.tcp.getTcpSender();
			sender.send( msg ) ;
			
		} catch (Exception e) {
			// 
			e.printStackTrace();
		}
	}
	
		
}	
	


class OutBox implements Observer,Runnable{

	boolean isWorking=false;
	NLPmain parent;
	Thread lptcpoThrd;
	ArrayList<NLPresultSet> deliveredResults= new ArrayList<NLPresultSet>();
	TcpBox tcp;
	StringedObjects so = new StringedObjects();
	
	
	// --------------------------------------------------------------------
	public OutBox(NLPmain nlmain) {
		parent = nlmain ;
	}
	// --------------------------------------------------------------------
	
	
	public void start() {
		// 
		lptcpoThrd = new Thread(this,"lptcpoThrd");
		lptcpoThrd.start() ;
	}

	@Override
	public void update(Observable sender, Object data) {
		//  usually from :  org.NooLab.openNLP.utilities.LanguageProcessorTools
		//        format :  org.NooLab.openNLP.NLPresultSet
		
		String cn = sender.getClass().getSimpleName().toLowerCase();
		
		parent.out.print(2, "NLPmain outBox received message...  from sender : "+sender.toString());
		parent.out.print(2, "                               ...         data : "+data.toString());
			if (data instanceof NLPresultSet){
				NLPresultSet pkg = (NLPresultSet)data;
				deliveredResults.add(pkg);
			}
		
	}
	
	protected void send(String msg){
		
		
		try {
		
			DataSenderIntf sender = tcp.getTcpSender();
			sender.send( msg ) ;
			
		}catch(Exception e){
			
		}
		
	}
	
	
	@Override
	public void run() {
		// 
		parent.outBoxIsRunning=true;
		isWorking=false;
		String str;
		tcp = parent.lpFactory.getTcpBox();
		
		while (parent.outBoxIsRunning){
			
			if ((isWorking==false) && (deliveredResults.size()>0)){
				isWorking=true;
				
				NLPresultSet resultSet = deliveredResults.get(0) ;
				
				/*
				 *  we need some further processings;
				 *  - verbs without VAFIN
				 *  - search terms = NN + main verbs, quantors like : none, all, not
				 */
				
				if (parent.lpProperties.activateForSearchTerms()){
					
					
					SearchTermsPreparation stp = new SearchTermsPreparation(parent.nlp.lingproc , resultSet);
					WordSets ws = (new SearchTermsPreparation( parent.lingproc , resultSet)).getWordSets( 0 ); // 0 = take all, 1 = remove included sets 
					
					if (ws!=null){
						
					}
				}
				
				if (resultSet==null){
					            parent.out.print(2, "Nlp, results object about to send was null.");
				}else{

					str = so.encode(resultSet) ;
					// sending through TCP to "surface"
								parent.out.print(2, "Nlp, sending encoded results object, length is "+str.length()+" chars.");
					DataSenderIntf sender = tcp.getTcpSender();
					try {
						
						sender.send( str ) ;
						parent.sentResultsCount++;
						
						if (parent.lpFactory.actions.isClosingScheduled()){
							parent.lpFactory.checkForClosing(parent.sentResultsCount , parent.receivedStringsCount);
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
	
				}
				
				deliveredResults.remove(0);
				
				isWorking=false;
			} // isWorking ?
			else{
				parent.out.delay(50);
			}
			
		}// ->
		parent.outBoxIsRunning=false;
	}



	
	
}



class InBox implements Observer,Runnable{

		boolean isWorking=false;
		NLPmain parent;
		Thread lptcpThrd;
		ArrayList<NlpDataRequest> acceptedStrings = new ArrayList<NlpDataRequest>();
		private boolean isDe;
		private boolean isEn;
		
		
		// --------------------------------------------------------------------
		public InBox(NLPmain nlmain) {
			parent = nlmain ;
		}
		// --------------------------------------------------------------------
		
		
		public void start() {
			// 
			lptcpThrd = new Thread(this,"lptcpiThrd");
			lptcpThrd.start() ;
		}

		@Override
		public void update(Observable sender, Object data) {
			// 
			Object obj =null;
			boolean formatOk = false;
			
			String cn  = sender.getClass().getSimpleName().toLowerCase();
			String cnd = data.getClass().getSimpleName().toLowerCase();
		
			parent.out.print(3, "NLPmain inBox received message...  from sender : "+sender.toString());
			
			if (cn.contains("tcp")){
				if (data instanceof String){
					String str = (String)data;
					
					addIncomingString(str);
					return;
					// ActionRequest ?
				}else{
					String str;
					if (cnd.contentEquals("string[]")){
						
						String[] datastrs = (String[])data;
						
						if (datastrs.length>1){
							str = datastrs[1];
						}else{
							str = datastrs[0]; 
						} // [] ?
						
						int blanks = parent.strgutil.frequencyOfStr(str, " ");
						
						// encoded object ?
						if ((str.startsWith("rO0")) && (str.length()>100) && (blanks<=1)){
						
							try{
								obj = parent.so.decode(str) ;
							}catch(Exception e){
							}
							
							if (obj instanceof ActionRequest){
								
								parent.lpFactory.handlingActionRequests( (ActionRequest)obj );
								formatOk=true;
							}
							
							if (obj instanceof NlpDataRequest){
								
								NlpDataRequest dataRequest = (NlpDataRequest)obj ;
								addIncomingString(dataRequest);
								formatOk=true;
								return;
							}
							
							if ((formatOk==false) && (obj!=null)){
								cn = obj.toString();
								parent.out.print(2, "unexpected object received : " + cn);
								cn = obj.getClass().getSimpleName();
							}
							
						}

						if ((str != null) && (str.length() > 5)) {
								addIncomingString(str);
								
								parent.receivedStringsCount++;
						} // str ?
						
					} // 
					else{
					// not string and also not string[]
						parent.out.printErr(1, "Unknown message format, message can't be processed."+
								   			   "Message type approximately "+data.toString() );
						
					}
				} // not "String" ?

				if (data instanceof NlpDataPackage){ 
					NlpDataPackage nlpPkg = (NlpDataPackage)data;
					ArrayList<String> items = nlpPkg.getTextitems() ;
					for (int i=0;i<items.size();i++){
						addIncomingString(items.get(i)) ;	
					}
					
				}
			
			}else{
				parent.out.printErr(1, "Unknown client, message can't be processed."+
									   "Message type approximately "+data.toString() );
			}
			
		}

 
		private void addIncomingString(String str) {
			NlpDataRequest dataRequest = new NlpDataRequest();
			
			dataRequest.setText(str) ;
			addIncomingString( dataRequest );
		}	
		
		private void addIncomingString(NlpDataRequest dataRequest) {
			
			if ((dataRequest!=null) && (dataRequest.getText().length()>0)){
				parent.out.print(2, "incoming string added to the queue (n="+acceptedStrings.size()+")");
				
				acceptedStrings.add(dataRequest);
			}else{
				parent.out.print(2,"received text container was empty.");
			}
		}
		
		@Override
		public void run() {
			// 
			parent.inBoxIsRunning=true;
			isWorking=false;
			String str;
			boolean rB,langOk ;
			String moduleLanguage = parent.lpProperties.getActiveLanguage().toLowerCase();
			
			while (parent.inBoxIsRunning){
				
				if ((isWorking==false) && (acceptedStrings.size()>0)){
					isWorking=true;
					
					parent.out.delay(200);
					
					langOk = false;
					
					NlpDataRequest rqPkg = acceptedStrings.get(0) ;
					str =  rqPkg.getText() ; 
					// the language for the module is defined apriori by the external caller !!!
					
					try{
						

						if (moduleLanguage.startsWith("de")){
							langOk=true;
							parent.lingproc.setText(str, "de") ;
							// performing the analysis
							parent.lingproc.setLang("de") ;
							parent.lingproc.setLanguageDetected(true);
							
							String guid = GUID.randomvalue() ;
							if (rqPkg.getGuid().length()>0){
								guid = rqPkg.getGuid();
							}
							parent.lingproc.analyze( guid );
						}
						if (moduleLanguage.startsWith("en")){
							langOk=true;
							parent.lingproc.setText(str, "en") ;
							// performing the analysis
							parent.lingproc.setLang("en") ;
							parent.lingproc.setLanguageDetected(true);
							
							String guid = GUID.randomvalue() ;
							if (rqPkg.getGuid().length()>0){
								guid = rqPkg.getGuid();
							}
							parent.lingproc.analyze(guid);
						}
						
					}catch(Exception e){
						langOk=false ;
					}
					
					
					if (langOk==false){
						parent.out.printErr(1, "Language not recognized or not available.");
						parent.sendFailureToClient( );
					}
					acceptedStrings.remove(0);
					isWorking=false;
				} // isWorking ?
				else{
					parent.out.delay(300);
				}
				
			}// ->
			
			parent.inBoxIsRunning = false;
		}
		
		
		
	}




